/**
 * 
 */
package cz.cuni.mff.abacs.burglar.logics.storage;

import cz.cuni.mff.abacs.burglar.logics.DataMap;
import cz.cuni.mff.abacs.burglar.logics.objects.Room;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Agent;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.BeliefBase;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Burglar;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Guard;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Inventory;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Item;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.*;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;


/**  
 * Creates a DOM XML tree and then prints the XML.
 * 
 * Static class.
 * 
 * @author abacs
 *
 */
public class XMLSaver extends XMLBase {
	
	
	// -------------------------------------------------------------------------
	// constructors:
	
	
	/**
	 * Private constructor to prevent object creation.
	 */
	private XMLSaver() {  }
	
	
	// -------------------------------------------------------------------------
	// public functions:
	
	
	/**
	 * Saves the map object to a file.
	 * 
	 * @param map map object to save.
	 * @param fileName name of the target file.
	 */
	public static void save(DataMap map, String fileName) {
		XMLSaver.writeToFile(generateXML(map), fileName);
	}
	
	
	/**
	 * Saves the map object to a stream.
	 * 
	 * @param map map object to save.
	 * @param outstream output stream as the target to write.
	 */
	public static void save(DataMap map, OutputStream outstream) {
		XMLSaver.writeToStream(generateXML(map), outstream);
	}
	
	
	/**
	 * Saves the map object to a string.
	 * 
	 * @param map map object to save.
	 */
	public static String generateXML(DataMap map) {
		try {
			// Creating an empty XML Document
			
			// We need a Document
			DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
			Document doc = docBuilder.newDocument();
			
			// Creating the XML tree
			
			// create the root element and add it to the document
			Element root = doc.createElement(STR_MAP);
			doc.appendChild(root);
			
			// create a comment and put it in the root element
			Comment comment = doc.createComment("generated by burglar-game");
			root.appendChild(comment);
			
			// create child element, add an attribute, and add to root
			
			for(Room room : map.getRooms())
				root.appendChild(XMLSaver.generateRoomXML(room, doc));
			
			for(Position position : map.getPositions())
				root.appendChild(XMLSaver.generatePositionXML(position, doc));		
			
			for(Agent agent : map.getAgents())
				root.appendChild(XMLSaver.generateAgentXML(agent, doc));
			
			root.appendChild(XMLSaver.generateNotesXML(map.getLevelNotes(), doc));
			
			// Output the XML
			
			// set up a transformer
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.INDENT, "yes");
			
			// create string from XML tree
			StringWriter sWriter = new StringWriter();
			StreamResult result = new StreamResult(sWriter);
			DOMSource source = new DOMSource(doc);
			trans.transform(source, result);
			
			return sWriter.toString();

		}catch (Exception e){
			System.err.println(e);
		}
		return "";
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/** 
	 * Creates a room XML object.
	 * 
	 * @param obj room object.
	 * @param doc document to write.
	 * @return The created XML element.
	 */
	private static Element generateRoomXML(Room obj, Document doc) {
		Element child = doc.createElement(STR_ROOM);
		
		int id = obj.getId();
		child.setAttribute(STR_ID, String.valueOf(id));
		int x = obj.getX();
		child.setAttribute(STR_X, String.valueOf(x));
		int y = obj.getY();
		child.setAttribute(STR_Y, String.valueOf(y));
		int width = obj.getWidth();
		child.setAttribute(STR_WIDTH, String.valueOf(width));
		int height = obj.getHeight();
		child.setAttribute(STR_HEIGHT, String.valueOf(height));
		boolean is_lit = obj.getIsLit();
		child.setAttribute(STR_IS_LIT, String.valueOf(is_lit));
		
		return child;
	}
	
	
	/** 
	 * Creates a position XML object.
	 * 
	 * @param obj position object.
	 * @param doc document to write.
	 * @return The created XML element.
	 */
	private static Element generatePositionXML(Position obj, Document doc) {
		switch(obj.getType()){
		case CAMERA:
			return generateCameraXML((Camera)obj, doc);
		case CONTAINER:
			return generateContainerXML((Container)obj, doc);
		case DOOR:
			return generateDoorXML((Door)obj, doc);
		case FLOOR:
			return generateFloorXML((Floor)obj, doc);
		case PHONE:
			return generatePhoneXML((Phone)obj, doc);
		case SWITCH:
			return generateSwitchXML((Switch)obj, doc);
		case VENDER:
			return generateVenderXML((Vender)obj, doc);
		default:
			return doc.createElement("position");
		}
	}
	
	
	/** 
	 * Creates an agent XML object.
	 * 
	 * @param obj agent object.
	 * @param doc document to write.
	 * @return The created XML element.
	 */
	private static Element generateAgentXML(Agent obj, Document doc) {
		
		switch(obj.getType()){
		case BURGLAR:
			return generateBurglarXML((Burglar)obj, doc);
		case GUARD:
			return generateGuardXML((Guard)obj, doc);
		default:
			return null;
		}
	}
	
	
	// -------------------------------------------------------------------------
	
	
	private static Element generateCameraXML(Camera camera, Document doc) {
		Element child = doc.createElement(STR_CAMERA);
		
		int roomId = camera.getRoomId();
		child.setAttribute(STR_ROOM, String.valueOf(roomId));
		child.setAttribute(STR_IS_ACTIVE, String.valueOf(camera.isActive()));
		
		setBasePositionAttributes(camera, child);
		
		return child;
	}
	
	
	private static Element generateContainerXML(
			Container container,
			Document doc
	) {
		Element child = doc.createElement(STR_CONTAINER);
		
		int roomId = container.getRoomId();
		child.setAttribute(STR_ROOM, String.valueOf(roomId));
		child.setAttribute(STR_IS_CLOSED, String.valueOf(container.isClosed()));
		child.setAttribute(STR_IS_LOCKED, String.valueOf(container.isLocked()));
		child.setAttribute(STR_KEY, String.valueOf(container.getKeyId()));
		
		child.appendChild(addInventoryXML(container, doc));
		
		setBasePositionAttributes(container, child);
		
		return child;
	}
	
	
	private static Element generateDoorXML(Door door, Document doc) {
		Element child = doc.createElement(STR_DOOR);
		
		int[] roomIds = door.getRoomIds();
		child.setAttribute(STR_FROM_ROOM, String.valueOf(roomIds[0]));
		child.setAttribute(STR_TO_ROOM,   String.valueOf(roomIds[1]));
		child.setAttribute(STR_IS_CLOSED, String.valueOf(door.isClosed()));
		child.setAttribute(STR_IS_LOCKED, String.valueOf(door.isLocked()));
		child.setAttribute(STR_KEY, String.valueOf(door.getKeyId()));
		
		setBasePositionAttributes(door, child);
		
		return child;
	}
	
	
	private static Element generateFloorXML(Floor floor, Document doc) {
		Element child = doc.createElement(STR_FLOOR);
		
		int roomId = floor.getRoomId();
		child.setAttribute(STR_ROOM, String.valueOf(roomId));
		
		setBasePositionAttributes(floor, child);
		
		return child;
	}
	
	
	private static Element generatePhoneXML(Phone phone, Document doc) {
		Element child = doc.createElement(STR_PHONE);
		
		int roomId = phone.getRoomId();
		child.setAttribute(STR_ROOM, String.valueOf(roomId));
		child.setAttribute(STR_IS_ACTIVE, String.valueOf(phone.isActive()));
		
		setBasePositionAttributes(phone, child);
		
		return child;
	}
	
	
	private static Element generateSwitchXML(Switch switchObj, Document doc) {
		Element child = doc.createElement(STR_SWITCH);
		
		int roomId = switchObj.getRoomId();
		child.setAttribute(STR_ROOM, String.valueOf(roomId));
		
		// TODO
		
		setBasePositionAttributes(switchObj, child);
		
		return child;
	}
	
	
	private static Element generateVenderXML(Vender vender, Document doc) {
		Element child = doc.createElement(STR_VENDER);
		
		int roomId = vender.getRoomId();
		child.setAttribute(STR_ROOM, String.valueOf(roomId));
		child.setAttribute(STR_HAS_DROPPED, String.valueOf(vender.hasDropped()));
		
		setBasePositionAttributes(vender, child);
		
		return child;
	}
	
	
	private static void setBasePositionAttributes(
			Position position,
			Element element
	) {
		int id = position.getId();
		element.setAttribute(STR_ID, String.valueOf(id));
		int x = position.getX();
		element.setAttribute(STR_X, String.valueOf(x));
		int y = position.getY();
		element.setAttribute(STR_Y, String.valueOf(y));
	}
	
	
	private static Element generateBurglarXML(Burglar burglar, Document doc) {
		Element child = doc.createElement(STR_BURGLAR);
		
		child.setAttribute(STR_POSITION, String.valueOf(burglar.getPositionId()));
		
		child.appendChild(addInventoryXML(burglar, doc));
		
		child.appendChild(addBurglarGoalXML(burglar, doc));
		
		child.appendChild(addBeliefBaseXML(burglar, doc));
		
		setBaseAgentAttributes(burglar, child);
		
		return child;
	}
	
	
	private static Element generateGuardXML(Guard guard, Document doc) {
		Element child = doc.createElement(STR_GUARD);
		
		child.setAttribute(STR_POSITION, String.valueOf(guard.getPositionId()));
		
		child.setAttribute(STR_IS_STUNNED, String.valueOf(guard.isInState(Agent.State.STUNNED)));
		
		
		child.appendChild(addInventoryXML(guard, doc));
		
		child.appendChild(addGuardGoalXML(guard, doc));
		
		child.appendChild(addBeliefBaseXML(guard, doc));
		
		setBaseAgentAttributes(guard, child);
		
		return child;
	}
	
	
	private static void setBaseAgentAttributes(
			Agent agent,
			Element element
	) {
		int id = agent.getId();
		element.setAttribute(STR_ID, String.valueOf(id));
		
		int positionId = agent.getPosition().getId();
		element.setAttribute(STR_POSITION, String.valueOf(positionId));
	}
	
	
	private static Element addBurglarGoalXML(
			Burglar burglar,
			Document doc
	) {
		Element goal = doc.createElement(STR_GOAL);
		
		goal.setAttribute(STR_AIM, String.valueOf(burglar.getAimId()));
		goal.setAttribute(STR_COLLECT, String.valueOf(burglar.getItemIdToCollect()));
		
		return goal;
	}
	
	
	private static Element addGuardGoalXML(
			Guard guard,
			Document doc
	) {
		Element goal = doc.createElement(STR_GOAL);
		
		for(Integer roomId : guard.getAllGoalRooms()){
			Element room = doc.createElement(STR_ROOM);
			room.setAttribute(STR_ID, String.valueOf(roomId));
			goal.appendChild(room);
		}
		
		return goal;
	}
	
	
	private static Element addInventoryXML(
			Inventory inv,
			Document doc
	) {
		Element inventory = doc.createElement(STR_INVENTORY);
		
		for(Item item : inv.getItems())
			inventory.appendChild(generateItemXML(item, doc));
		
		return inventory;
	}
	
	
	private static Element generateItemXML(
			Item item,
			Document doc
	) {
		Element element;
		switch(item.getType()){
		case KEY:
			element = doc.createElement(STR_KEY);
			break;
		case LADDER:
			element = doc.createElement(STR_LADDER);
			break;
		case TREASURE:
			element = doc.createElement(STR_TREASURE);
			break;
		default:
			return null;
		}
		int id = item.getId();
		element.setAttribute(STR_ID, String.valueOf(id));
		return element;
	}
	
	
	private static Element addBeliefBaseXML(
			Agent agent,
			Document doc
	) {
		Element bBaseElement = doc.createElement(STR_BELIEFBASE);
		BeliefBase bb = agent.getBeliefBase();
		for(Position position : bb.getKnownPositions()){
			Element posElement = generatePositionXML(position, doc);
			bBaseElement.appendChild(posElement);
		}
		return bBaseElement;
	}
	
	
	private static Element generateNotesXML(List<String> notes, Document doc) {
		Element notesElement = doc.createElement(STR_NOTES);
		
		for(String note : notes){
			Element noteElement = doc.createElement(STR_NOTE);
			noteElement.setTextContent(note);
			notesElement.appendChild(noteElement);
		}
		
		return notesElement;
	}
	
	
}
